Termination check: handle recursion through proof trees and add recursive types check#2006
Termination check: handle recursion through proof trees and add recursive types check#2006
Conversation
|
Ok, many thanks for that! There is a lot of things to review, and I need to think about this, because there is a lot of potential for unsoundness. |
4718c2c to
e032a4d
Compare
I came up with an argument for that. You can split every trait in two: its associated types on one side, and its methods and supertrait constraints on the other side. Once associated type declarations are viewed as standalone constructs, they depend on nothing. That's why there is no need to track dependencies between ADTs and associated type declarations (which are different from associated trait instances, for which I do track dependencies). The only remaining dependencies from types to traits are via trait objects, which are basically not supported at the moment. |
|
Cannot associated types depend on associated types of subtraits? |
|
I don't see how. Another way to think about it is that associated types are like extra type parameters (as they would actually be in Rocq), with differences only relevant to type inference. trait Tr {
type A;
}
struct S<T: Tr>(T::A);
// is like
trait Tr<A> {
}
struct S<A, T: Tr<A>>(A); |
b449dfd to
64422e4
Compare
6603c32 to
7b072b6
Compare
7b072b6 to
2d02416
Compare
jhjourdan
left a comment
There was a problem hiding this comment.
That's very good work, which solves many difficult problems in Creusot. Thanks.
Why did you not use the proof tree API from rustc, finally?
Do we agree that this does not fully addresses #1232, since it does not solve the problem with default functions?
|
|
||
| The attribute `#[trusted(positive(T))]` can be used on a `struct` or `enum` | ||
| to postulate that certain arguments are strictly positive. | ||
|
|
There was a problem hiding this comment.
| The attribute `#[trusted(positive(T))]` can be used on a `struct` or `enum` | |
| to postulate that certain arguments are strictly positive. Note that Creusot | |
| does not check anything about this annotation (hence the use of the | |
| `#[trusted` attribute). It is the user's responsibility to not use this annotation | |
| on non-strictly positive types. |
| ``` | ||
|
|
||
| If this were accepted by Creusot, the associated type `Dummy::A` would be equivalent to | ||
| `Mapping<Dummy::A, bool>`, which would be unsound as explained in a [previous counterexample](#counterexample). |
| /// Cell over predicates | ||
| /// | ||
| /// A wrapper around `std::cell::Cell` that allows predicates to be used to specify the contents of the cell. | ||
| #[trusted(positive(T))] |
There was a problem hiding this comment.
I'm really not sure for this one. A PredCell<T> is essentially a predicate over T, so it is essentially negative. I really suspect one can build a contradiction using this.
|
|
||
| impl ProgramPurity { | ||
| pub fn is_ghost(self) -> bool { | ||
| self == ProgramPurity::Ghost |
There was a problem hiding this comment.
| self == ProgramPurity::Ghost | |
| self >= ProgramPurity::Ghost |
(More robust wrt. maintainance.)
| } | ||
| } | ||
|
|
||
| pub fn select_method<'tcx>( |
There was a problem hiding this comment.
| pub fn select_method<'tcx>( | |
| fn select_method<'tcx>( |
| s.valid() | ||
| } | ||
|
|
||
| fn as_predicates<'tcx>( |
There was a problem hiding this comment.
| fn as_predicates<'tcx>( | |
| fn trait_refs_of_clauses<'tcx>( |
| // Some bounds can be trait aliases and we skip them. | ||
| // The implied bounds should already appear separately. |
There was a problem hiding this comment.
I've never understood whether this is indeed the case. Do we have a test for that?
(I know that this is unrelated to this PR, but still, that's good to check that now.)
| let ImplSelection::Found(source) = select_trait_impl(tcx, typing_env, trait_ref) else { | ||
| continue; | ||
| }; | ||
| let ImplSource::UserDefined(source) = source else { continue }; |
There was a problem hiding this comment.
So in the end you decided to not use the new ProofTree API?
This seems good like this, apart from the fact that I'm afraid that we will miss some dependencies due to builtin impls.
For now, I don't this this should be a problem, because Creusot does not handle these builtin impls anyway (see #2094). But with this implementation technique, we are missing dependencies introduced by impls for Clone.
For a list of them (for Clone only), see instantiate_constituent_tys_for_copy_clone_trait in rustc source. I have not found any other builtin trait that could cause this issue (but there might be some, including in the future).
| let args = tcx.mk_args_from_iter( | ||
| trait_ref.args.iter().chain(trait_item_args.iter().skip(trait_ref.args.len())), | ||
| ); |
There was a problem hiding this comment.
That's exactly rebase_onto.
| tgt: graph::NodeIndex, | ||
| path: &mut Vec<graph::NodeIndex>, | ||
| visited: &mut HashSet<graph::NodeIndex>, | ||
| ) -> Result<(), ()> { |
There was a problem hiding this comment.
It's a bit weird to use Result for that, because the error case is actually not an error, it is what we are searching for.
Close #1232
#[trusted(ghost)](more fine grained than#[trusted] #[check(ghost)], i.e., it still enables Coma translation of program functions)#[trusted(terminates)](idem, and can also be used on types)#[trusted(positive(T, U))]to postulate "strictly positive" type arguments (can also be used inextern_spec!)Some related things that are not handled in this PR:
struct Never(Box<Never>), Reject empty types before Why3 rejects them. #881)Finally, I put traits in the same dependency graph as types, but there are currently no edges between traits and types. When checking traits, I only find dependencies to other traits in trait bounds (for instance,
trait Tr0 where T1: Tr2<T3>has an edgeTr0 -> Tr2, andT1andT3are ignored). And when checking types, there are no edges due to bounds (instruct T<A: Tr>(A),Tris ignored; but we will track dependencies to trait impls wheneverTis instantiated) or trait objectsdyn Tr(because they are unsupported). Can you find a counterexample where that breaks soundness?